#include "window_view.h"

#include "base/log/log.h"

extern std::thread::id g_mainThreadId;

namespace Keels {
namespace Rosen {
void WaitForRSRenderThreadInit();
} // namespace Rosen
namespace Ace::NG {
void OhosInitializeRenderPipeline();
void OhosResetNodeId();
} // namespace Ace::NG
} // namespace Keels

namespace {
const constexpr char KEELS_XCOMPONENT_PREFIX[] = "Keels_XComponent_";
const constexpr char KEELS_OHOS_WINDOW_SUFFIX[] = "_Keels_OHOS_Window";
const constexpr uint32_t KEELS_XCOMPONENT_BACKGROUND_COLOR = 0xFFF2F3F5;
const constexpr uint32_t KEEES_WINDOW_ID_INITIAL_VALUE = 0x000F0000;
} // namespace

namespace Keels::Ace::Platform {
WindowViewManager::WindowViewManager()
{
    if (isInitialized_) {
        return;
    }
    OH_ArkUI_GetModuleInterface(ARKUI_NATIVE_NODE, ArkUI_NativeNodeAPI_1, api_);
    callback_.OnSurfaceCreated = OnSurfaceCreated;
    callback_.OnSurfaceChanged = OnSurfaceChanged;
    callback_.OnSurfaceDestroyed = OnSurfaceDestroyed;
    callback_.DispatchTouchEvent = DispatchTouchEvent;
    windowCnt_ = KEEES_WINDOW_ID_INITIAL_VALUE;
    isInitialized_ = true;
}

WindowViewManager& WindowViewManager::GetInstance()
{
    static WindowViewManager inst {};
    return inst;
}

ArkUI_NativeNodeAPI_1* WindowViewManager::GetNodeApi()
{
    return GetInstance().api_;
}

OH_NativeXComponent_Callback* WindowViewManager::GetXComponentCallback()
{
    return &GetInstance().callback_;
}

uint32_t WindowViewManager::GetNewWindowId()
{
    auto id = GetInstance().windowCnt_++;
    if (id == UINT32_MAX) {
        LOGE("[WindowViewManager::GetNewWindowId][ID overflow]");
    }
    return id;
}

std::string WindowViewManager::GetXComponentId(OH_NativeXComponent* component)
{
    if (component == nullptr) {
        LOGE("[WindowViewManager::GetXComponentId][component is nullptr]");
        return "";
    }
    char id[OH_XCOMPONENT_ID_LEN_MAX + 1] {};
    size_t size = OH_XCOMPONENT_ID_LEN_MAX + 1;
    auto errCode = OH_NativeXComponent_GetXComponentId(component, id, &size);
    if (errCode != 0) {
        LOGE("[WindowViewManager::GetXComponentId][Get id from native XComponent failed][component:%p]", component);
        return "";
    }
    LOGD("[WindowViewManager::GetXComponentId][component:%p][id:%s]", component, id);
    return std::string { id, size };
}

std::shared_ptr<Rosen::Window> WindowViewManager::GetWindow(OH_NativeXComponent* component)
{
    auto xComponentId = GetXComponentId(component);
    return Rosen::Window::FindWindow(xComponentId + KEELS_OHOS_WINDOW_SUFFIX);
}

void WindowViewManager::RegisterWindowView(OH_NativeXComponent* component, WindowView* windowView)
{
    auto ret = GetInstance().componentToWindowViewMap_.emplace(component, windowView);
    if (!ret.second) {
        // [TOOD] log error
    }
}

void WindowViewManager::UnregisterWindowView(OH_NativeXComponent* component)
{
    GetInstance().componentToWindowViewMap_.erase(component);
}

void WindowViewManager::OnSurfaceCreated(OH_NativeXComponent* component, void* window)
{
    LOGD("[WindowViewManager::OnSurfaceCreated][called][component:%p][window:%p]", component, window);
    g_mainThreadId = std::this_thread::get_id();
    auto rsWindow = GetWindow(component);
    if (rsWindow == nullptr) {
        LOGE("[WindowViewManager::OnSurfaceCreated][Get rsWindow failed][component:%p][window:%p]", component, window);
        return;
    }
    rsWindow->SetUIContent("EntryView", false, nullptr, false);
    rsWindow->CreateSurfaceNode(window);
    Keels::Ace::NG::OhosInitializeRenderPipeline();
    Keels::Rosen::WaitForRSRenderThreadInit();
    auto it = GetInstance().componentToWindowViewMap_.find(component);
    if (it == GetInstance().componentToWindowViewMap_.end() || it->second == nullptr) {
        LOGE(
            "[WindowViewManager::OnSurfaceCreated][Get windowView failed][component:%p][window:%p]", component, window);
        return;
    }
    it->second->isReady_ = true;
}

void WindowViewManager::OnSurfaceChanged(OH_NativeXComponent* component, void* window)
{
    LOGD("[WindowViewManager::OnSurfaceChanged][called][component:%p][window:%p]", component, window);
    auto rsWindow = GetWindow(component);
    if (rsWindow == nullptr) {
        LOGE("[WindowViewManager::OnSurfaceChanged][Get rsWindow failed][component:%p][window:%p]", component, window);
        return;
    }
    uint64_t width, height;
    OH_NativeXComponent_GetXComponentSize(component, window, &width, &height);
    rsWindow->NotifySurfaceChanged(width, height, Keels::Ace::SystemProperties::GetResolution());
}

void WindowViewManager::OnSurfaceDestroyed(OH_NativeXComponent* component, void* window)
{
    LOGD("[WindowViewManager::OnSurfaceDestroyed][called][component:%p][window:%p]", component, window);
    auto rsWindow = GetWindow(component);
    if (rsWindow == nullptr) {
        LOGE(
            "[WindowViewManager::OnSurfaceDestroyed][Get rsWindow failed][component:%p][window:%p]", component, window);
        return;
    }
    rsWindow->NotifySurfaceDestroyed();
    rsWindow->Destroy();
    Keels::Ace::NG::OhosResetNodeId();
    auto it = GetInstance().componentToWindowViewMap_.find(component);
    if (it == GetInstance().componentToWindowViewMap_.end() || it->second == nullptr) {
        LOGE("[WindowViewManager::OnSurfaceDestroyed][Get windowView failed][component:%p][window:%p]", component,
            window);
        return;
    }
    it->second->isDestroyed_ = true;
}

void WindowViewManager::DispatchTouchEvent(OH_NativeXComponent* component, void* window)
{
    LOGD("[WindowViewManager::DispatchTouchEvent][called][component:%p][window:%p]", component, window);
    auto rsWindow = GetWindow(component);
    if (rsWindow == nullptr) {
        LOGE(
            "[WindowViewManager::DispatchTouchEvent][Get rsWindow failed][component:%p][window:%p]", component, window);
        return;
    }
    OH_NativeXComponent_TouchEvent touchEvent {};
    int32_t ret = OH_NativeXComponent_GetTouchEvent(component, window, &touchEvent);
    if (ret != 0) {
        LOGE("[WindowViewManager::DispatchTouchEvent][Get TouchEvent failed][component:%p][window:%p]", component,
            window);
        return;
    }
    if (!rsWindow->ProcessPointerEvent(component, touchEvent)) {
        LOGE("[WindowViewManager::DispatchTouchEvent][ProcessPointerEvent failed][component:%p][window:%p]", component,
            window);
    }
}

WindowView::WindowView()
{
    Initialize();
}

WindowView::~WindowView()
{
    if (component_ != nullptr) {
        WindowViewManager::UnregisterWindowView(component_);
        component_ = nullptr;
    }
    DisposeNodeHandle();
}

ArkUI_NodeHandle WindowView::GetNodeHandle() const
{
    return xComponent_;
}

void WindowView::DisposeNodeHandle()
{
    if (xComponent_ != nullptr) {
        WindowViewManager::GetNodeApi()->disposeNode(xComponent_);
        xComponent_ = nullptr;
    }
}

bool WindowView::IsReady() const
{
    return isReady_;
}

bool WindowView::IsDestroyed() const
{
    return isDestroyed_;
}

void WindowView::UpdateLayout(float width, float height, float left, float top)
{
    auto api = WindowViewManager::GetNodeApi();
    if (api == nullptr) {
        LOGE("[WindowView::UpdateSize][GetNodeApi failed]");
        return;
    }
    if (xComponent_ == nullptr) {
        LOGE("[WindowView::UpdateSize][xComponent_ is null]");
        return;
    }

    ArkUI_NumberValue widthValue[] = { { .f32 = width } };
    ArkUI_AttributeItem widthItem = { widthValue, sizeof(widthValue) / sizeof(ArkUI_NumberValue) };
    api->setAttribute(xComponent_, NODE_WIDTH, &widthItem);

    ArkUI_NumberValue heightValue[] = { { .f32 = height } };
    ArkUI_AttributeItem heightItem = { heightValue, sizeof(heightValue) / sizeof(ArkUI_NumberValue) };
    api->setAttribute(xComponent_, NODE_HEIGHT, &heightItem);

    ArkUI_NumberValue positionValue[] = { { .f32 = left }, { .f32 = top } };
    ArkUI_AttributeItem positionItem = { positionValue, sizeof(positionValue) / sizeof(ArkUI_NumberValue) };
    api->setAttribute(xComponent_, NODE_POSITION, &positionItem);
}

void WindowView::MountThirdPartyUIRoot(const void* uiRoot)
{
    if (uiRoot == nullptr) {
        LOGE("[WindowView::MountThirdPartyUIRoot] third-party UI root node is null");
        return;
    }
    auto rsWindow = rsWindow_.lock();
    if (rsWindow == nullptr) {
        LOGE("[WindowView::MountThirdPartyUIRoot] rsWindow_ is null");
        return;
    }
    rsWindow->MountThirdPartyUIRoot(uiRoot);
}

void WindowView::UnmountThirdPartyUIRoot()
{
    auto rsWindow = rsWindow_.lock();
    if (rsWindow == nullptr) {
        LOGE("[WindowView::UnmountThirdPartyUIRoot] rsWindow_ is null");
        return;
    }
    rsWindow->UnmountThirdPartyUIRoot();
}

void WindowView::NodeEventReceiver(ArkUI_NodeEvent*)
{
    LOGD("WindowView::NodeEventReceiver");
}

void WindowView::Initialize()
{
    auto api = WindowViewManager::GetNodeApi();
    if (api == nullptr) {
        LOGE("[WindowView::Initialize][GetNodeApi failed]");
        return;
    }

    auto id = WindowViewManager::GetNewWindowId();
    auto xComponentId = KEELS_XCOMPONENT_PREFIX + std::to_string(id);
    auto name = xComponentId + KEELS_OHOS_WINDOW_SUFFIX;
    rsWindow_ = Rosen::Window::Create(name, id);

    xComponent_ = api->createNode(ARKUI_NODE_XCOMPONENT);
    if (xComponent_ == nullptr) {
        LOGE("[WindowView::Initialize][Create XComponent failed]");
        return;
    }

    ArkUI_NumberValue typeValue[] = { { .u32 = ARKUI_XCOMPONENT_TYPE_SURFACE } };
    ArkUI_AttributeItem typeItem = { typeValue, sizeof(typeValue) / sizeof(ArkUI_NumberValue) };
    api->setAttribute(xComponent_, NODE_XCOMPONENT_TYPE, &typeItem);

    ArkUI_AttributeItem idItem = { .string = xComponentId.c_str() };
    api->setAttribute(xComponent_, NODE_XCOMPONENT_ID, &idItem);

    ArkUI_NumberValue bgColorValue[] = { { .u32 = KEELS_XCOMPONENT_BACKGROUND_COLOR } };
    ArkUI_AttributeItem bgColorItem = { bgColorValue, sizeof(bgColorValue) / sizeof(ArkUI_NumberValue) };
    api->setAttribute(xComponent_, NODE_BACKGROUND_COLOR, &bgColorItem);

    api->addNodeEventReceiver(xComponent_, WindowView::NodeEventReceiver);
    api->registerNodeEvent(xComponent_, NODE_ON_CLICK, 0, nullptr);

    component_ = OH_NativeXComponent_GetNativeXComponent(xComponent_);
    if (component_ == nullptr) {
        LOGE("[WindowView::Initialize][Get native XComponent failed]");
        return;
    }
    auto err = OH_NativeXComponent_RegisterCallback(component_, WindowViewManager::GetXComponentCallback());
    if (err != 0) {
        LOGE("[WindowView::Initialize][Register native XComponent callbacks failed][err:%{public}d]", err);
    }
    WindowViewManager::RegisterWindowView(component_, this);
}
} // namespace Keels::Ace::Platform